#include "precompiled.hpp"
#include "interpreter.hpp"
#include "expand.hpp"
#include "saved_rules.hpp"
#include "detail/ScopeGuard.h"
#include "glob.hpp"

jam_interpeter::jam_interpeter(jam_context_ptr context) : context_(context)
{
}

void jam_interpeter::intepret_tree(const parse_node_base *node, jam_module_ptr module, jam_list *res)
{
	if (!module) module=context_->get_root_module();	

	jam_frame cur_frame(*context_,module,node);
	dispatch_node(node,&cur_frame,res);
}

void jam_interpeter::intepret_tree(const parse_node_base *node, jam_module_ptr module, 
	jam_frame *cur_frame,jam_list *res)
{
	if (!module) module=context_->get_root_module();	
	dispatch_node(node,cur_frame,res);
}

void jam_interpeter::dispatch_node(const parse_node_base *node, jam_frame *frame, jam_list *res)
{
	//And now... The Monster Switch!
	switch(node->get_node_id())
	{
		case rules_node::node_id :
		{
			const rules_node *rules=static_cast<const rules_node*>(node);
			dispatch_node(rules->rule_tree_.get(),frame,res);
			//TODO: transform recursion into iteration
			dispatch_node(rules->chained_rules_tree_.get(),frame,res);
		}
		break;

		case rule_node::node_id :
		{
			const rule_node *rule=static_cast<const rule_node*>(node);
			evaluate_rule(rule,frame,res);
		}
		break;

		case append_node::node_id :
		{
			const append_node *append=static_cast<const append_node*>(node);
			dispatch_node(append->left_tree_.get(),frame,res);
			dispatch_node(append->right_tree_.get(),frame,res);
		}
		break;

		case list_node::node_id :
		{
			const list_node *lst=static_cast<const list_node*>(node);
			assert(res);
			expand_string(frame->args_,*frame->module_,lst->expand_string_,*res);
		}
		break;

		case set_node::node_id :
		{
			const set_node *setnode=static_cast<const set_node*>(node);
			evaluate_set(setnode,frame,res);
		}
		break;

		case foreach_node::node_id :
		{
			const foreach_node *fornode=static_cast<const foreach_node*>(node);
			evaluate_for(fornode,frame,res);
		}
		break;

		case setcomp_node::node_id :
		{
			const setcomp_node *savenode=static_cast<const setcomp_node*>(node);
			evaluate_save_rule(savenode,frame,res);
		}
		break;

		case if_node::node_id :
		{
			const if_node *ifnode=static_cast<const if_node*>(node);
			evaluate_if(ifnode,frame,res);
		}
		break;

		case while_node::node_id :
		{
			const while_node *whilenode=static_cast<const while_node*>(node);
			evaluate_while(whilenode,frame,res);
		}
		break;

		case eval_node::node_id :
		{
			const eval_node *eval=static_cast<const eval_node*>(node);
			if (evaluate_eval_real(eval,frame))
				*res=true_list;
		}
		break;

		case switch_node::node_id :
		{
			const switch_node *switchnode=static_cast<const switch_node*>(node);
			evaluate_switch(switchnode,frame,res);
		}
		break;

		case local_node::node_id :
		{
			const local_node *localnode=static_cast<const local_node*>(node);
			evaluate_local(localnode,frame,res);
		}
		break;

		case module_node::node_id :
		{
			const module_node *mod=static_cast<const module_node*>(node);
			evaluate_module(mod,frame,res);
		}
		break;

		case return_node::node_id :
		{
			const return_node *ret=static_cast<const return_node*>(node);
			dispatch_node(ret->list_res_.get(),frame,res);
		}
		break;

		case left_brace_node::node_id :
		{
			const left_brace_node *lb=static_cast<const left_brace_node*>(node);
			dispatch_node(lb->body_tree_.get(),frame,res);
		}
		break;

		case right_brace_node::node_id :
		assert(false);
		break;

		case null_node::node_id :
		break;

		default:
			assert(false); //@nc			
	}
}

void jam_interpeter::evaluate_rule(const rule_node *node, jam_frame *frame, jam_list *res)
{
	//Update current node
	frame->cur_node_=node; //TODO: maybe do it in dispatch_node?

	//Rule frame encompasses its arguments. It also serves as for backtracing in error reports.	
	jam_frame rule_frame(*context_,frame,node);

	//params_tree must be a lol-none - parse it into rule arguments.
	//Note rule parameters are parsed in the parrent context, not in the
	//context of a rule
	parse_lol(node->params_tree_.get(), frame, rule_frame.args_);

	jam_list names; //Expand rule name
	expand_string(frame->args_,*frame->module_,node->name_,names);

	if (names.empty())
	{
		context_->backtrace_line(&rule_frame);
		context_->err()<<"warning: rulename '"<<node->name_<<"' expands to empty string"<<std::endl;
		return;
	}
	const jam_string &rule_name=names.front();
	rule_frame.scope_name_=rule_name;

	if (!names.size_is_one())
	{
		//If name expansion returned more than one result - push it at the
		//beginning of arguments list.
		names.pop_front();
		rule_frame.args_.push_front(names);
	}

	jam_module::rule_ent rule;
	bool rule_found=context_->bind_rule(rule_name,rule_frame.module_,&rule);
	if (!rule_found)
	{
		context_->err()<<"rule "<<rule_name
			<<" unknown in module "<<rule_frame.module_->get_name()<<std::endl;
		context_->backtrace(&rule_frame);
		throw jam_exception("Jam interpreter exception");
	}

	//Propagate rule module down the call stack
	if (rule.is_localized_) //Localized rules must be executed in the calling module
		rule_frame.module_=frame->module_;
	else
		rule_frame.module_=rule.rule_->get_module(); //TODO: CHECK THIS!!!!!

	rule.rule_->invoke(&rule_frame,res);
}


void jam_interpeter::parse_lol(const parse_node_base *node, jam_frame *frame, list_of_lists &res)
{
	assert(node->get_node_id()==lol_node::node_id);
	const lol_node *lol=static_cast<const lol_node*>(node);	

	bool is_first=true;
	bool has_a_chain=!is_null(lol->chained_list_.get());
	while(true)
	{
		//bool has_chain=!is_null(lol->chained_list_.get());

		//TODO: I don't like this. May be a source of strange errors...
		if (has_a_chain /*!is_first */|| !is_null(lol->list_elem_.get()))
		{
			//Get list element and expand it
			res.push_back();
			if (lol->list_elem_)
				dispatch_node(lol->list_elem_.get(),frame,&res.back());
		}

		if (is_null(lol->chained_list_.get()))
			break;
		lol=static_cast<const lol_node*>(lol->chained_list_.get());
		is_first=false;
	};
}

//NOTE: this is THE ONLY place where we MODIFY the returned results rather than append
//to it
void jam_interpeter::evaluate_set_real(const jam_list &names, const parse_node_base *val_tree, 
	assignment_operation operation, jam_frame *frame, jam_list *res)
{	
	jam_module &mod=*frame->module_;

	if (names.size_is_one())
	{
		jam_list &val=mod.get_var_value_ref(names.front());	
		if (operation==ASSIGN_DEFAULT && !val.empty())
			; //Do nothing for 'default' assignment
		else if (operation==ASSIGN_APPEND)
		{
			//This will append results directly to the variable. Neat!
			jam_list tmp_val;
			dispatch_node(val_tree,frame,&tmp_val);
			val.move_to_tail(tmp_val);
		}
		else
		{
			assert(operation==ASSIGN_SET);
			jam_list tmp_val;
			dispatch_node(val_tree,frame,&tmp_val);
			val.swap(tmp_val);
			if (res) *res=val;
		}
	} else
	{
		jam_list tmp_val;
		dispatch_node(val_tree,frame,&tmp_val);
		for(jam_list::const_iterator f=names.begin(),fend=names.end();f!=fend;++f)
			mod.set_var_value(*f,tmp_val,operation);
		//TODO: this works ORDER OF MAGNITUDE times faster on MSVS than in-place
		//construction. I don't know why ;(
		if (res) *res=tmp_val;
	}
}

void jam_interpeter::evaluate_set(const set_node *node, jam_frame *frame, jam_list *res)
{
	jam_list names;
	dispatch_node(node->var_names_tree_.get(),frame,&names);
	evaluate_set_real(names,node->val_tree_.get(),node->operation_,frame,res);
}

void jam_interpeter::evaluate_local(const local_node *node, jam_frame *frame, jam_list *)
{
	jam_list names;
	dispatch_node(node->var_list_tree_.get(),frame,&names);
	size_t name_sz=names.size(),name_pos=0;

	//TODO: replace this with a nice stack-oriented container!
	DYN_STACK_ARRAY(var_saver,savers,name_sz);
	try
	{
		//Save variable values
		for(jam_list::const_iterator f=names.begin(),fend=names.end();f!=fend;++f)
		{	
			new((void*)&savers[name_pos]) var_saver(&frame->module_->get_var_value_ref(*f));
			name_pos++;
		}
		assert(name_pos==name_sz);

		//Note that callees of the current context get this "local"
		//variable, making it not so much local as layered.
		//Now I understand why Emacs has 'dynamic scoping' :)))
		jam_list tmp_res;
		evaluate_set_real(names,node->val_list_tree_.get(),ASSIGN_SET,
			frame,NULL); //TODO: is NULL acceptable here?
		dispatch_node(node->rule_list_tree_.get(),frame,&tmp_res);

		//Restore saved variables
		for(size_t n=0;n<name_pos;++n) savers[n].~var_saver();
	} catch (...)
	{
		//Restore saved variables
		for(size_t n=0;n<name_pos;++n) savers[n].~var_saver();
		throw;
	}
}

void jam_interpeter::evaluate_for(const foreach_node *rule, jam_frame *frame, jam_list *res)
{
	const jam_string &idx=rule->index_var_name_;
	jam_module &mod=*frame->module_;

	//Save value of index variable if it was declared local
	var_saver saver(rule->is_var_local_?&mod.get_var_value_ref(idx):NULL);

	jam_list values;
	dispatch_node(rule->var_values_tree_.get(),frame,&values);
		
	jam_list &idx_value=mod.get_var_value_ref(idx);
	//Set the initial value so we won't trip assert during the first front() call.
	idx_value=jam_string(boost::cref("Init"),5); 
	for(jam_list::const_iterator f=values.begin(),fend=values.end();f!=fend;++f)
	{		
		idx_value.front()=*f;
		dispatch_node(rule->rule_tree_.get(),frame,NULL); //We're not interested in return result
	}

	if (res) *res=null_list; //Yes. It's strange.
}

void jam_interpeter::evaluate_save_rule(const setcomp_node *rule, jam_frame *frame, jam_list *)
{
	list_of_lists args;
	parse_lol(rule->param_list_tree_.get(),frame,args);

	std::auto_ptr<saved_rule> saved(new saved_rule(context_.get(),
		frame->module_.get(),rule,args));
	frame->module_->register_rule(saved.release(),!rule->is_local_);
}

void jam_interpeter::evaluate_if(const if_node *rule, jam_frame *frame, jam_list *res)
{
	assert(rule->condition_tree_->get_node_id()==eval_node::node_id);
	const eval_node *ev=static_cast<const eval_node*>(rule->condition_tree_.get());
		
	if(evaluate_eval_real(ev,frame))
		dispatch_node(rule->then_tree_.get(),frame,res);
	else
		dispatch_node(rule->else_tree_.get(),frame,res);		
}

void jam_interpeter::evaluate_while(const while_node *rule, jam_frame *frame, jam_list *res)
{
	assert(rule->condition_tree_->get_node_id()==eval_node::node_id);
	const eval_node *ev=static_cast<const eval_node*>(rule->condition_tree_.get());

	while(true)
	{		
		if(!evaluate_eval_real(ev,frame))
			break;
		dispatch_node(rule->body_tree_.get(),frame,res);
	}
}

int jam_interpeter::lcmp(const jam_list &t, const jam_list &s)
{
	int status = 0;

	size_t sz1=t.size(),sz2=s.size();
	if (sz1!=sz2) return sz1<sz2?-1:1;

    jam_list::const_iterator tf=t.begin(),tfend=t.end(),sf=s.begin(),sfend=s.end();
	for(;tf!=tfend && sf!=sfend; ++sf, ++tf)
	{	
		int res=tf->compare(*sf);
		if (res) return res;
	}

	assert(tf==tfend && sf==sfend); //@nc
	return 0;
}

bool jam_interpeter::evaluate_eval_real(const eval_node *rule, jam_frame *frame)
{
	jam_list left;
	dispatch_node(rule->left_tree_.get(),frame,&left);

	//Short circuit lr eval for &&, ||, and 'in'	
	switch(rule->operation_)
	{
	case EXPR_AND: 
	case EXPR_IN: 
		if(left.empty()) return false;
	break;
	case EXPR_OR: 
		if(!left.empty()) return true;
	break;
	default:; //Need full evaluation
	}
	
	jam_list right;
	dispatch_node(rule->right_tree_.get(),frame,&right);

	switch(rule->operation_)
	{
	case EXPR_NOT:	
		return (left.empty());

	case EXPR_AND:
		return (!left.empty() && !right.empty());

	case EXPR_OR:
		return (!left.empty() || !right.empty());

	case EXPR_IN:
		{
			//"a in b": make sure each of
			//left is equal to something in right.
			//TODO: maybe need to add a temporary set to make it O(N*logN)?

			for(jam_list::const_iterator f=left.begin(),fend=left.end();
				f!=fend;++f)
			{
				if (std::find(right.begin(),right.end(),*f)==right.end())
					return false;
			}
			return true;
		}

	case EXPR_EXISTS:   return lcmp(left, null_list) != 0;
	case EXPR_EQUALS:	return lcmp(left, right) == 0;
	case EXPR_NOTEQ:	return lcmp(left, right) != 0;
	case EXPR_LESS:		return lcmp(left, right) < 0 ;
	case EXPR_LESSEQ:	return lcmp(left, right) <= 0;
	case EXPR_MORE:		return lcmp(left, right) > 0 ;
	case EXPR_MOREEQ:	return lcmp(left, right) >= 0;

	default:
		assert(false); //@nc
		return false; //@nc - suppress compiler warning
	}
}

void jam_interpeter::evaluate_switch(const switch_node *rule, jam_frame *frame, jam_list *res)
{
	jam_list val;
	dispatch_node(rule->value_tree_.get(),frame,&val);
	const jam_string &str_val=val.empty()?jam_string():val.front();
		
	//Skip the brace node
	assert(rule->case_tree_->get_node_id()==left_brace_node::node_id);
	const left_brace_node *lbr=static_cast<const left_brace_node*>(rule->case_tree_.get());

	//Step through cases
	const parse_node_base *cur=lbr->body_tree_.get();
	while(!is_null(cur))
	{
		assert(cur->get_node_id()==chain_node::node_id);
		const chain_node *node=static_cast<const chain_node*>(cur);
		
		assert(node->left_tree_->get_node_id()==case_node::node_id);
		const case_node *cur_case=static_cast<const case_node *>(node->left_tree_.get());
		if(!glob(cur_case->value_.c_str(), str_val.c_str()))
		{
			//Get & exec parse tree for this case
			dispatch_node(cur_case->body_tree_.get(),frame,res);
			return;
		}

		cur=node->right_tree_.get();
	}	
}

void jam_interpeter::evaluate_module(const module_node *mod, jam_frame *frame, jam_list *res)
{
	jam_list module;
	jam_module_ptr new_module;
	dispatch_node(mod->module_name_tree_.get(),frame,&module);	
	if (module.empty())
		new_module=context_->get_root_module();
	else
		new_module=context_->bind_module(module.front());

	//Create module frame
	jam_frame mod_frame(*frame);
	mod_frame.module_=new_module;
	//Evaluate module content
	dispatch_node(mod->module_content_tree_.get(),&mod_frame,res);
}
